package org.training.apr.dao.fake;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;


/**
 * @author Miguel Guinada
 * 
 * IMPORTANT NOTICE: This fake DAO implementation assumes that all domain objects have a 
 * <code>public Long getId()</code> method that returns an unique identifier for the model object  
 */
public abstract class FakeDao<T> {
	private Map<Long, T> map = new TreeMap<Long, T>();
	
	public FakeDao() {
	}
	
	protected List<T> uniqueResult(List<T> list) {
		if(list.size() > 1) {
			throw new FakeDaoException("collection should not have more than one result");
		}
		return list;
	}
	
	public void save(T t) {
		map.put(invokeGetId(t), t);
	}
	
	private Long invokeGetId(T t) {
		try {
			Method method = t.getClass().getMethod("getId");
			Object id = method.invoke(t);
			if(!(id instanceof Long)) {
				throw new FakeDaoException("Method getId() has to return Long at " + t.getClass());
			}
			
			return (Long) id;
		} catch (SecurityException e) {
			handleReflectiveOperationException(t, e);
		} catch (NoSuchMethodException e) {
			handleReflectiveOperationException(t, e);
		} catch (IllegalArgumentException e) {
			handleReflectiveOperationException(t, e);
		} catch (IllegalAccessException e) {
			handleReflectiveOperationException(t, e);
		} catch (InvocationTargetException e) {
			handleReflectiveOperationException(t, e);
		}
		return null;
	}

	private void handleReflectiveOperationException(T t, Exception e) {
		throw new FakeDaoException("Method 'public Long getId()' could not be invoked at " + t.getClass(), e);
	}

	public T read(Long id) {
		return map.get(id);
	}
	
	public void delete(T t) {
		map.remove(invokeGetId(t));
	}
	
	public List<T> findAll() {
		return new ArrayList<T>(map.values());
	}

	public List<T> findByCriteria(Criteria<T> criteria) {
		List<T> result = new ArrayList<T>();
		
		for(T t: findAll()) {
			if(criteria.evaluate(t)) {
				result.add(t);
			}
		}
		
		return result;
	}
	
	public void dump() {
		for(T t : findAll()) {
			System.out.println(t);
		}
	}
}